/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: SerializerBase.java,v 1.5 2006/04/14 12:09:19 sunithareddy Exp $
 */
package com.sun.org.apache.xml.internal.serializer;

import java.io.IOException;
import java.util.Vector;

import javax.xml.transform.SourceLocator;
import javax.xml.transform.Transformer;

import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
import com.sun.org.apache.xml.internal.serializer.utils.Utils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.Locator2;


/**
 * This class acts as a base class for the XML "serializers"
 * and the stream serializers.
 * It contains a number of common fields and methods.
 *
 * @xsl.usage internal
 */
public abstract class SerializerBase
    implements SerializationHandler, SerializerConstants {


  /**
   * To fire off the end element trace event
   *
   * @param name Name of element
   */
  protected void fireEndElem(String name)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT, name, (Attributes) null);
    }
  }

  /**
   * Report the characters trace event
   *
   * @param chars content of characters
   * @param start starting index of characters to output
   * @param length number of characters to output
   */
  protected void fireCharEvent(char[] chars, int start, int length)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CHARACTERS, chars, start, length);
    }
  }

  /**
   * true if we still need to call startDocumentInternal()
   */
  protected boolean m_needToCallStartDocument = true;

  /**
   * True if a trailing "]]>" still needs to be written to be
   * written out. Used to merge adjacent CDATA sections
   */
  protected boolean m_cdataTagOpen = false;

  /**
   * All the attributes of the current element, collected from
   * startPrefixMapping() calls, or addAddtribute() calls, or
   * from the SAX attributes in a startElement() call.
   */
  protected AttributesImplSerializer m_attributes = new AttributesImplSerializer();

  /**
   * Tells if we're in an EntityRef event.
   */
  protected boolean m_inEntityRef = false;

  /**
   * This flag is set while receiving events from the external DTD
   */
  protected boolean m_inExternalDTD = false;

  /**
   * The System ID for the doc type.
   */
  private String m_doctypeSystem;

  /**
   * The public ID for the doc type.
   */
  private String m_doctypePublic;

  /**
   * Flag to tell that we need to add the doctype decl, which we can't do
   * until the first element is encountered.
   */
  boolean m_needToOutputDocTypeDecl = true;

  /**
   * The character encoding.  Must match the encoding used for the
   * printWriter.
   */
  private String m_encoding = null;

  /**
   * Tells if we should write the XML declaration.
   */
  private boolean m_shouldNotWriteXMLHeader = false;

  /**
   * The standalone value for the doctype.
   */
  private String m_standalone;

  /**
   * True if standalone was specified.
   */
  protected boolean m_standaloneWasSpecified = false;

  /**
   * Determine if the output is a standalone.
   */
  protected boolean m_isStandalone = false;

  /**
   * Flag to tell if indenting (pretty-printing) is on.
   */
  protected boolean m_doIndent = false;
  /**
   * Amount to indent.
   */
  protected int m_indentAmount = 0;

  /**
   * Tells the XML version, for writing out to the XML decl.
   */
  private String m_version = null;

  /**
   * The mediatype.  Not used right now.
   */
  private String m_mediatype;

  /**
   * The transformer that was around when this output handler was created (if
   * any).
   */
  private Transformer m_transformer;

  /**
   * Pairs of local names and corresponding URIs of CDATA sections. This list
   * comes from the cdata-section-elements attribute. Every second one is a
   * local name, and every other second one is the URI for the local name.
   */
  protected Vector m_cdataSectionElements = null;

  /**
   * Namespace support, that keeps track of currently defined
   * prefix/uri mappings. As processed elements come and go, so do
   * the associated mappings for that element.
   */
  protected NamespaceMappings m_prefixMap;

  /**
   * Handle for firing generate events.  This interface may be implemented
   * by the referenced transformer object.
   */
  protected SerializerTrace m_tracer;

  protected SourceLocator m_sourceLocator;


  /**
   * The writer to send output to. This field is only used in the ToStream
   * serializers, but exists here just so that the fireStartDoc() and
   * other fire... methods can flush this writer when tracing.
   */
  protected java.io.Writer m_writer = null;

  /**
   * A reference to "stack frame" corresponding to
   * the current element. Such a frame is pushed at a startElement()
   * and popped at an endElement(). This frame contains information about
   * the element, such as its namespace URI.
   */
  protected ElemContext m_elemContext = new ElemContext();

  /**
   * A utility buffer for converting Strings passed to
   * character() methods to character arrays.
   * Reusing this buffer means not creating a new character array
   * everytime and it runs faster.
   */
  protected char[] m_charsBuff = new char[60];

  /**
   * A utility buffer for converting Strings passed to
   * attribute methods to character arrays.
   * Reusing this buffer means not creating a new character array
   * everytime and it runs faster.
   */
  protected char[] m_attrBuff = new char[30];

  private Locator m_locator = null;

  protected boolean m_needToCallSetDocumentInfo = true;

  /**
   * Receive notification of a comment.
   *
   * @see ExtendedLexicalHandler#comment(String)
   */
  public void comment(String data) throws SAXException {
    final int length = data.length();
    if (length > m_charsBuff.length) {
      m_charsBuff = new char[length * 2 + 1];
    }
    data.getChars(0, length, m_charsBuff, 0);
    comment(m_charsBuff, 0, length);
  }

  /**
   * If at runtime, when the qname of the attribute is
   * known, another prefix is specified for the attribute, then we can
   * patch or hack the name with this method. For
   * a qname of the form "ns?:otherprefix:name", this function patches the
   * qname by simply ignoring "otherprefix".
   * TODO: This method is a HACK! We do not have access to the
   * XML file, it sometimes generates a NS prefix of the form "ns?" for
   * an attribute.
   */
  protected String patchName(String qname) {

    final int lastColon = qname.lastIndexOf(':');

    if (lastColon > 0) {
      final int firstColon = qname.indexOf(':');
      final String prefix = qname.substring(0, firstColon);
      final String localName = qname.substring(lastColon + 1);

      // If uri is "" then ignore prefix
      final String uri = m_prefixMap.lookupNamespace(prefix);
      if (uri != null && uri.length() == 0) {
        return localName;
      } else if (firstColon != lastColon) {
        return prefix + ':' + localName;
      }
    }
    return qname;
  }

  /**
   * Returns the local name of a qualified name. If the name has no prefix,
   * then it works as the identity (SAX2).
   *
   * @param qname the qualified name
   * @return the name, but excluding any prefix and colon.
   */
  protected static String getLocalName(String qname) {
    final int col = qname.lastIndexOf(':');
    return (col > 0) ? qname.substring(col + 1) : qname;
  }

  /**
   * Receive an object for locating the origin of SAX document events.
   *
   * @param locator An object that can return the location of any SAX document event.
   *
   * Receive an object for locating the origin of SAX document events.
   *
   * <p>SAX parsers are strongly encouraged (though not absolutely required) to supply a locator: if
   * it does so, it must supply the locator to the application by invoking this method before
   * invoking any of the other methods in the DocumentHandler interface.</p>
   *
   * <p>The locator allows the application to determine the end position of any document-related
   * event, even if the parser is not reporting an error.  Typically, the application will use this
   * information for reporting its own errors (such as character content that does not match an
   * application's business rules).  The information returned by the locator is probably not
   * sufficient for use with a search engine.</p>
   *
   * <p>Note that the locator will return correct information only during the invocation of the
   * events in this interface.  The application should not attempt to use it at any other time.</p>
   */
  public void setDocumentLocator(Locator locator) {
    m_locator = locator;
  }

  /**
   * Adds the given attribute to the set of collected attributes , but only if
   * there is a currently open element.
   *
   * An element is currently open if a startElement() notification has
   * occured but the start of the element has not yet been written to the
   * output.  In the stream case this means that we have not yet been forced
   * to close the elements opening tag by another notification, such as a
   * character notification.
   *
   * @param uri the URI of the attribute
   * @param localName the local name of the attribute
   * @param rawName the qualified name of the attribute
   * @param type the type of the attribute (probably CDATA)
   * @param value the value of the attribute
   * @param XSLAttribute true if this attribute is coming from an xsl:attriute element
   * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
   */
  public void addAttribute(
      String uri,
      String localName,
      String rawName,
      String type,
      String value,
      boolean XSLAttribute)
      throws SAXException {
    if (m_elemContext.m_startTagOpen) {
      addAttributeAlways(uri, localName, rawName, type, value, XSLAttribute);
    }

  }

  /**
   * Adds the given attribute to the set of attributes, even if there is
   * no currently open element. This is useful if a SAX startPrefixMapping()
   * should need to add an attribute before the element name is seen.
   *
   * @param uri the URI of the attribute
   * @param localName the local name of the attribute
   * @param rawName the qualified name of the attribute
   * @param type the type of the attribute (probably CDATA)
   * @param value the value of the attribute
   * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
   * @return true if the attribute was added, false if an existing value was replaced.
   */
  public boolean addAttributeAlways(
      String uri,
      String localName,
      String rawName,
      String type,
      String value,
      boolean XSLAttribute) {
    boolean was_added;
//            final int index =
//                (localName == null || uri == null) ?
//                m_attributes.getIndex(rawName):m_attributes.getIndex(uri, localName);
    int index;
//            if (localName == null || uri == null){
//                index = m_attributes.getIndex(rawName);
//            }
//            else {
//                index = m_attributes.getIndex(uri, localName);
//            }

    if (localName == null || uri == null || uri.length() == 0) {
      index = m_attributes.getIndex(rawName);
    } else {
      index = m_attributes.getIndex(uri, localName);
    }
    if (index >= 0) {
                /* We've seen the attribute before.
                 * We may have a null uri or localName, but all
                 * we really want to re-set is the value anyway.
                 */
      m_attributes.setValue(index, value);
      was_added = false;
    } else {
      // the attribute doesn't exist yet, create it
      m_attributes.addAttribute(uri, localName, rawName, type, value);
      was_added = true;
    }
    return was_added;

  }


  /**
   * Adds  the given attribute to the set of collected attributes,
   * but only if there is a currently open element.
   *
   * @param name the attribute's qualified name
   * @param value the value of the attribute
   */
  public void addAttribute(String name, final String value) {
    if (m_elemContext.m_startTagOpen) {
      final String patchedName = patchName(name);
      final String localName = getLocalName(patchedName);
      final String uri = getNamespaceURI(patchedName, false);

      addAttributeAlways(uri, localName, patchedName, "CDATA", value, false);
    }
  }

  /**
   * Adds the given xsl:attribute to the set of collected attributes,
   * but only if there is a currently open element.
   *
   * @param name the attribute's qualified name (prefix:localName)
   * @param value the value of the attribute
   * @param uri the URI that the prefix of the name points to
   */
  public void addXSLAttribute(String name, final String value, final String uri) {
    if (m_elemContext.m_startTagOpen) {
      final String patchedName = patchName(name);
      final String localName = getLocalName(patchedName);

      addAttributeAlways(uri, localName, patchedName, "CDATA", value, true);
    }
  }

  /**
   * Add the given attributes to the currently collected ones. These
   * attributes are always added, regardless of whether on not an element
   * is currently open.
   *
   * @param atts List of attributes to add to this list
   */
  public void addAttributes(Attributes atts) throws SAXException {

    int nAtts = atts.getLength();
    for (int i = 0; i < nAtts; i++) {
      String uri = atts.getURI(i);

      if (null == uri) {
        uri = "";
      }

      addAttributeAlways(
          uri,
          atts.getLocalName(i),
          atts.getQName(i),
          atts.getType(i),
          atts.getValue(i),
          false);

    }
  }

  /**
   * Return a {@link ContentHandler} interface into this serializer.
   * If the serializer does not support the {@link ContentHandler}
   * interface, it should return null.
   *
   * @return A {@link ContentHandler} interface into this serializer, or null if the serializer is
   * not SAX 2 capable
   * @throws IOException An I/O exception occured
   */
  public ContentHandler asContentHandler() throws IOException {
    return this;
  }

  /**
   * Report the end of an entity.
   *
   * @param name The name of the entity that is ending.
   * @throws org.xml.sax.SAXException The application may raise an exception.
   * @see #startEntity
   */
  public void endEntity(String name) throws org.xml.sax.SAXException {
    if (name.equals("[dtd]")) {
      m_inExternalDTD = false;
    }
    m_inEntityRef = false;

    if (m_tracer != null) {
      this.fireEndEntity(name);
    }
  }

  /**
   * Flush and close the underlying java.io.Writer. This method applies to
   * ToStream serializers, not ToSAXHandler serializers.
   *
   * @see ToStream
   */
  public void close() {
    // do nothing (base behavior)
  }

  /**
   * Initialize global variables
   */
  protected void initCDATA() {
    // CDATA stack
    //        _cdataStack = new Stack();
    //        _cdataStack.push(new Integer(-1)); // push dummy value
  }

  /**
   * Returns the character encoding to be used in the output document.
   *
   * @return the character encoding to be used in the output document.
   */
  public String getEncoding() {
    return m_encoding;
  }

  /**
   * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
   *
   * @param m_encoding the character encoding
   */
  public void setEncoding(String m_encoding) {
    this.m_encoding = m_encoding;
  }

  /**
   * Sets the value coming from the xsl:output omit-xml-declaration stylesheet attribute
   *
   * @param b true if the XML declaration is to be omitted from the output document.
   */
  public void setOmitXMLDeclaration(boolean b) {
    this.m_shouldNotWriteXMLHeader = b;
  }


  /**
   * @return true if the XML declaration is to be omitted from the output document.
   */
  public boolean getOmitXMLDeclaration() {
    return m_shouldNotWriteXMLHeader;
  }

  /**
   * Returns the previously set value of the value to be used as the public
   * identifier in the document type declaration (DTD).
   *
   * @return the public identifier to be used in the DOCTYPE declaration in the output document.
   */
  public String getDoctypePublic() {
    return m_doctypePublic;
  }

  /**
   * Set the value coming from the xsl:output doctype-public stylesheet attribute.
   *
   * @param doctypePublic the public identifier to be used in the DOCTYPE declaration in the output
   * document.
   */
  public void setDoctypePublic(String doctypePublic) {
    this.m_doctypePublic = doctypePublic;
  }


  /**
   * Returns the previously set value of the value to be used
   * as the system identifier in the document type declaration (DTD).
   *
   * @return the system identifier to be used in the DOCTYPE declaration in the output document.
   */
  public String getDoctypeSystem() {
    return m_doctypeSystem;
  }

  /**
   * Set the value coming from the xsl:output doctype-system stylesheet attribute.
   *
   * @param doctypeSystem the system identifier to be used in the DOCTYPE declaration in the output
   * document.
   */
  public void setDoctypeSystem(String doctypeSystem) {
    this.m_doctypeSystem = doctypeSystem;
  }

  /**
   * Set the value coming from the xsl:output doctype-public and doctype-system stylesheet
   * properties
   *
   * @param doctypeSystem the system identifier to be used in the DOCTYPE declaration in the output
   * document.
   * @param doctypePublic the public identifier to be used in the DOCTYPE declaration in the output
   * document.
   */
  public void setDoctype(String doctypeSystem, String doctypePublic) {
    this.m_doctypeSystem = doctypeSystem;
    this.m_doctypePublic = doctypePublic;
  }

  /**
   * Sets the value coming from the xsl:output standalone stylesheet attribute.
   *
   * @param standalone a value of "yes" indicates that the <code>standalone</code> delaration is to
   * be included in the output document. This method remembers if the value was explicitly set using
   * this method, verses if the value is the default value.
   */
  public void setStandalone(String standalone) {
    if (standalone != null) {
      m_standaloneWasSpecified = true;
      setStandaloneInternal(standalone);
    }
  }

  /**
   * Sets the XSL standalone attribute, but does not remember if this is a
   * default or explicite setting.
   *
   * @param standalone "yes" | "no"
   */
  protected void setStandaloneInternal(String standalone) {
    if ("yes".equals(standalone)) {
      m_standalone = "yes";
    } else {
      m_standalone = "no";
    }

  }

  /**
   * Gets the XSL standalone attribute
   *
   * @return a value of "yes" if the <code>standalone</code> delaration is to be included in the
   * output document.
   * @see XSLOutputAttributes#getStandalone()
   */
  public String getStandalone() {
    return m_standalone;
  }

  /**
   * @return true if the output document should be indented to visually indicate its structure.
   */
  public boolean getIndent() {
    return m_doIndent;
  }

  /**
   * Gets the mediatype the media-type or MIME type associated with the output
   * document.
   *
   * @return the mediatype the media-type or MIME type associated with the output document.
   */
  public String getMediaType() {
    return m_mediatype;
  }

  /**
   * Gets the version of the output format.
   *
   * @return the version of the output format.
   */
  public String getVersion() {
    return m_version;
  }

  /**
   * Sets the value coming from the xsl:output version attribute.
   *
   * @param version the version of the output format.
   * @see SerializationHandler#setVersion(String)
   */
  public void setVersion(String version) {
    m_version = version;
  }

  /**
   * Sets the value coming from the xsl:output media-type stylesheet attribute.
   *
   * @param mediaType the non-null media-type or MIME type associated with the output document.
   * @see javax.xml.transform.OutputKeys#MEDIA_TYPE
   * @see SerializationHandler#setMediaType(String)
   */
  public void setMediaType(String mediaType) {
    m_mediatype = mediaType;
  }

  /**
   * @return the number of spaces to indent for each indentation level.
   */
  public int getIndentAmount() {
    return m_indentAmount;
  }

  /**
   * Sets the indentation amount.
   *
   * @param m_indentAmount The m_indentAmount to set
   */
  public void setIndentAmount(int m_indentAmount) {
    this.m_indentAmount = m_indentAmount;
  }

  /**
   * Sets the value coming from the xsl:output indent stylesheet
   * attribute.
   *
   * @param doIndent true if the output document should be indented to visually indicate its
   * structure.
   * @see XSLOutputAttributes#setIndent(boolean)
   */
  public void setIndent(boolean doIndent) {
    m_doIndent = doIndent;
  }

  /**
   * Sets the isStandalone property
   *
   * @param isStandalone true if the ORACLE_IS_STANDALONE is set to yes
   * @see OutputPropertiesFactory ORACLE_IS_STANDALONE
   */
  public void setIsStandalone(boolean isStandalone) {
    m_isStandalone = isStandalone;
  }

  /**
   * This method is used when a prefix/uri namespace mapping
   * is indicated after the element was started with a
   * startElement() and before and endElement().
   * startPrefixMapping(prefix,uri) would be used before the
   * startElement() call.
   *
   * @param uri the URI of the namespace
   * @param prefix the prefix associated with the given URI.
   * @see ExtendedContentHandler#namespaceAfterStartElement(String, String)
   */
  public void namespaceAfterStartElement(String uri, String prefix)
      throws SAXException {
    // default behavior is to do nothing
  }

  /**
   * Return a {@link DOMSerializer} interface into this serializer. If the
   * serializer does not support the {@link DOMSerializer} interface, it should
   * return null.
   *
   * @return A {@link DOMSerializer} interface into this serializer,  or null if the serializer is
   * not DOM capable
   * @throws IOException An I/O exception occured
   * @see Serializer#asDOMSerializer()
   */
  public DOMSerializer asDOMSerializer() throws IOException {
    return this;
  }

  /**
   * Push a boolean state based on if the name of the current element
   * is found in the list of qnames.  A state is only pushed if
   * there were some cdata-section-names were specified.
   * <p>
   * Hidden parameters are the vector of qualified elements specified in
   * cdata-section-names attribute, and the m_cdataSectionStates stack
   * onto which whether the current element is in the list is pushed (true or
   * false). Other hidden parameters are the current elements namespaceURI,
   * localName and qName
   */
  protected boolean isCdataSection() {

    boolean b = false;

    if (null != m_cdataSectionElements) {
      if (m_elemContext.m_elementLocalName == null) {
        m_elemContext.m_elementLocalName =
            getLocalName(m_elemContext.m_elementName);
      }
      if (m_elemContext.m_elementURI == null) {
        String prefix = getPrefixPart(m_elemContext.m_elementName);
        if (prefix != null) {
          m_elemContext.m_elementURI =
              m_prefixMap.lookupNamespace(prefix);
        }

      }

      if ((null != m_elemContext.m_elementURI)
          && m_elemContext.m_elementURI.length() == 0) {
        m_elemContext.m_elementURI = null;
      }

      int nElems = m_cdataSectionElements.size();

      // loop through 2 at a time, as these are pairs of URI and localName
      for (int i = 0; i < nElems; i += 2) {
        String uri = (String) m_cdataSectionElements.elementAt(i);
        String loc = (String) m_cdataSectionElements.elementAt(i + 1);
        if (loc.equals(m_elemContext.m_elementLocalName)
            && subPartMatch(m_elemContext.m_elementURI, uri)) {
          b = true;

          break;
        }
      }
    }
    return b;
  }

  /**
   * Tell if two strings are equal, without worry if the first string is null.
   *
   * @param p String reference, which may be null.
   * @param t String reference, which may be null.
   * @return true if strings are equal.
   */
  private static final boolean subPartMatch(String p, String t) {
    return (p == t) || ((null != p) && (p.equals(t)));
  }

  /**
   * Returns the local name of a qualified name.
   * If the name has no prefix,
   * then it works as the identity (SAX2).
   *
   * @param qname a qualified name
   * @return returns the prefix of the qualified name, or null if there is no prefix.
   */
  protected static final String getPrefixPart(String qname) {
    final int col = qname.indexOf(':');
    return (col > 0) ? qname.substring(0, col) : null;
    //return (col > 0) ? qname.substring(0,col) : "";
  }

  /**
   * Some users of the serializer may need the current namespace mappings
   *
   * @return the current namespace mappings (prefix/uri)
   * @see ExtendedContentHandler#getNamespaceMappings()
   */
  public NamespaceMappings getNamespaceMappings() {
    return m_prefixMap;
  }

  /**
   * Returns the prefix currently pointing to the given URI (if any).
   *
   * @param namespaceURI the uri of the namespace in question
   * @return a prefix pointing to the given URI (if any).
   * @see ExtendedContentHandler#getPrefix(String)
   */
  public String getPrefix(String namespaceURI) {
    String prefix = m_prefixMap.lookupPrefix(namespaceURI);
    return prefix;
  }

  /**
   * Returns the URI of an element or attribute. Note that default namespaces
   * do not apply directly to attributes.
   *
   * @param qname a qualified name
   * @param isElement true if the qualified name is the name of an element.
   * @return returns the namespace URI associated with the qualified name.
   */
  public String getNamespaceURI(String qname, boolean isElement) {
    String uri = EMPTYSTRING;
    int col = qname.lastIndexOf(':');
    final String prefix = (col > 0) ? qname.substring(0, col) : EMPTYSTRING;

    if (!EMPTYSTRING.equals(prefix) || isElement) {
      if (m_prefixMap != null) {
        uri = m_prefixMap.lookupNamespace(prefix);
        if (uri == null && !prefix.equals(XMLNS_PREFIX)) {
          throw new RuntimeException(
              Utils.messages.createMessage(
                  MsgKey.ER_NAMESPACE_PREFIX,
                  new Object[]{qname.substring(0, col)}));
        }
      }
    }
    return uri;
  }

  /**
   * Returns the URI of prefix (if any)
   *
   * @param prefix the prefix whose URI is searched for
   * @return the namespace URI currently associated with the prefix, null if the prefix is
   * undefined.
   */
  public String getNamespaceURIFromPrefix(String prefix) {
    String uri = null;
    if (m_prefixMap != null) {
      uri = m_prefixMap.lookupNamespace(prefix);
    }
    return uri;
  }

  /**
   * Entity reference event.
   *
   * @param name Name of entity
   */
  public void entityReference(String name) throws org.xml.sax.SAXException {

    flushPending();

    startEntity(name);
    endEntity(name);

    if (m_tracer != null) {
      fireEntityReference(name);
    }
  }

  /**
   * Sets the transformer associated with this serializer
   *
   * @param t the transformer associated with this serializer.
   * @see SerializationHandler#setTransformer(Transformer)
   */
  public void setTransformer(Transformer t) {
    m_transformer = t;

    // If this transformer object implements the SerializerTrace interface
    // then assign m_tracer to the transformer object so it can be used
    // to fire trace events.
    if ((m_transformer instanceof SerializerTrace) &&
        (((SerializerTrace) m_transformer).hasTraceListeners())) {
      m_tracer = (SerializerTrace) m_transformer;
    } else {
      m_tracer = null;
    }
  }

  /**
   * Gets the transformer associated with this serializer
   *
   * @return returns the transformer associated with this serializer.
   * @see SerializationHandler#getTransformer()
   */
  public Transformer getTransformer() {
    return m_transformer;
  }

  /**
   * This method gets the nodes value as a String and uses that String as if
   * it were an input character notification.
   *
   * @param node the Node to serialize
   */
  public void characters(org.w3c.dom.Node node)
      throws org.xml.sax.SAXException {
    flushPending();
    String data = node.getNodeValue();
    if (data != null) {
      final int length = data.length();
      if (length > m_charsBuff.length) {
        m_charsBuff = new char[length * 2 + 1];
      }
      data.getChars(0, length, m_charsBuff, 0);
      characters(m_charsBuff, 0, length);
    }
  }


  /**
   * @see org.xml.sax.ErrorHandler#error(SAXParseException)
   */
  public void error(SAXParseException exc) throws SAXException {
  }

  /**
   * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
   */
  public void fatalError(SAXParseException exc) throws SAXException {

    m_elemContext.m_startTagOpen = false;

  }

  /**
   * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
   */
  public void warning(SAXParseException exc) throws SAXException {
  }

  /**
   * To fire off start entity trace event
   *
   * @param name Name of entity
   */
  protected void fireStartEntity(String name)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF, name);
    }
  }

  /**
   * Report the characters event
   * @param chars  content of characters
   * @param start  starting index of characters to output
   * @param length  number of characters to output
   */
//    protected void fireCharEvent(char[] chars, int start, int length)
//        throws org.xml.sax.SAXException
//    {
//        if (m_tracer != null)
//            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CHARACTERS, chars, start,length);
//    }
//

  /**
   * This method is only used internally when flushing the writer from the
   * various fire...() trace events.  Due to the writer being wrapped with
   * SerializerTraceWriter it may cause the flush of these trace events:
   * EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS
   * EVENTTYPE_OUTPUT_CHARACTERS
   * which trace the output written to the output stream.
   */
  private void flushMyWriter() {
    if (m_writer != null) {
      try {
        m_writer.flush();
      } catch (IOException ioe) {

      }
    }
  }

  /**
   * Report the CDATA trace event
   *
   * @param chars content of CDATA
   * @param start starting index of characters to output
   * @param length number of characters to output
   */
  protected void fireCDATAEvent(char[] chars, int start, int length)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_CDATA, chars, start, length);
    }
  }

  /**
   * Report the comment trace event
   *
   * @param chars content of comment
   * @param start starting index of comment to output
   * @param length number of characters to output
   */
  protected void fireCommentEvent(char[] chars, int start, int length)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer
          .fireGenerateEvent(SerializerTrace.EVENTTYPE_COMMENT, new String(chars, start, length));
    }
  }


  /**
   * To fire off end entity trace event
   *
   * @param name Name of entity
   */
  public void fireEndEntity(String name)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
    }
    // we do not need to handle this.
  }

  /**
   * To fire off start document trace  event
   */
  protected void fireStartDoc()
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTDOCUMENT);
    }
  }


  /**
   * To fire off end document trace event
   */
  protected void fireEndDoc()
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDDOCUMENT);
    }
  }

  /**
   * Report the start element trace event. This trace method needs to be
   * called just before the attributes are cleared.
   *
   * @param elemName the qualified name of the element
   */
  protected void fireStartElem(String elemName)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_STARTELEMENT,
          elemName, m_attributes);
    }
  }

  /**
   * To fire off the end element event
   * @param name Name of element
   */
//    protected void fireEndElem(String name)
//        throws org.xml.sax.SAXException
//    {
//        if (m_tracer != null)
//            m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENDELEMENT,name, (Attributes)null);
//    }


  /**
   * To fire off the PI trace event
   *
   * @param name Name of PI
   */
  protected void fireEscapingEvent(String name, String data)
      throws org.xml.sax.SAXException {

    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_PI, name, data);
    }
  }


  /**
   * To fire off the entity reference trace event
   *
   * @param name Name of entity reference
   */
  protected void fireEntityReference(String name)
      throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      flushMyWriter();
      m_tracer.fireGenerateEvent(SerializerTrace.EVENTTYPE_ENTITYREF, name, (Attributes) null);
    }
  }

  /**
   * Receive notification of the beginning of a document.
   * This method is never a self generated call,
   * but only called externally.
   *
   * <p>The SAX parser will invoke this method only once, before any
   * other methods in this interface or in DTDHandler (except for
   * setDocumentLocator).</p>
   *
   * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception.
   */
  public void startDocument() throws org.xml.sax.SAXException {

    // if we do get called with startDocument(), handle it right away
    startDocumentInternal();
    m_needToCallStartDocument = false;
    return;
  }

  /**
   * This method handles what needs to be done at a startDocument() call,
   * whether from an external caller, or internally called in the
   * serializer.  For historical reasons the serializer is flexible to
   * startDocument() not always being called.
   * Even if no external call is
   * made into startDocument() this method will always be called as a self
   * generated internal startDocument, it handles what needs to be done at a
   * startDocument() call.
   *
   * This method exists just to make sure that startDocument() is only ever
   * called from an external caller, which in principle is just a matter of
   * style.
   */
  protected void startDocumentInternal() throws org.xml.sax.SAXException {
    if (m_tracer != null) {
      this.fireStartDoc();
    }

  }

  /* This method extracts version and encoding information from SAX events.
   */
  protected void setDocumentInfo() {
    if (m_locator == null) {
      return;
    }
    try {
      String strVersion = ((Locator2) m_locator).getXMLVersion();
      if (strVersion != null) {
        setVersion(strVersion);
      }
            /*String strEncoding = ((Locator2)m_locator).getEncoding();
            if (strEncoding != null)
                setEncoding(strEncoding); */

    } catch (ClassCastException e) {
    }
  }

  /**
   * This method is used to set the source locator, which might be used to
   * generated an error message.
   *
   * @param locator the source locator
   * @see ExtendedContentHandler#setSourceLocator(javax.xml.transform.SourceLocator)
   */
  public void setSourceLocator(SourceLocator locator) {
    m_sourceLocator = locator;
  }


  /**
   * Used only by TransformerSnapshotImpl to restore the serialization
   * to a previous state.
   *
   * @param mappings NamespaceMappings
   */
  public void setNamespaceMappings(NamespaceMappings mappings) {
    m_prefixMap = mappings;
  }

  public boolean reset() {
    resetSerializerBase();
    return true;
  }

  /**
   * Reset all of the fields owned by SerializerBase
   */
  private void resetSerializerBase() {
    this.m_attributes.clear();
    this.m_cdataSectionElements = null;
    this.m_elemContext = new ElemContext();
    this.m_doctypePublic = null;
    this.m_doctypeSystem = null;
    this.m_doIndent = false;
    this.m_encoding = null;
    this.m_indentAmount = 0;
    this.m_inEntityRef = false;
    this.m_inExternalDTD = false;
    this.m_mediatype = null;
    this.m_needToCallStartDocument = true;
    this.m_needToOutputDocTypeDecl = false;
    if (this.m_prefixMap != null) {
      this.m_prefixMap.reset();
    }
    this.m_shouldNotWriteXMLHeader = false;
    this.m_sourceLocator = null;
    this.m_standalone = null;
    this.m_standaloneWasSpecified = false;
    this.m_tracer = null;
    this.m_transformer = null;
    this.m_version = null;
    // don't set writer to null, so that it might be re-used
    //this.m_writer = null;
  }

  /**
   * Returns true if the serializer is used for temporary output rather than
   * final output.
   *
   * This concept is made clear in the XSLT 2.0 draft.
   */
  final boolean inTemporaryOutputState() {
        /* This is a hack. We should really be letting the serializer know
         * that it is in temporary output state with an explicit call, but
         * from a pragmatic point of view (for now anyways) having no output
         * encoding at all, not even the default UTF-8 indicates that the serializer
         * is being used for temporary RTF.
         */
    return (getEncoding() == null);

  }

  /**
   * This method adds an attribute the the current element,
   * but should not be used for an xsl:attribute child.
   *
   * @see ExtendedContentHandler#addAttribute(java.lang.String, java.lang.String, java.lang.String,
   * java.lang.String, java.lang.String)
   */
  public void addAttribute(String uri, String localName, String rawName, String type, String value)
      throws SAXException {
    if (m_elemContext.m_startTagOpen) {
      addAttributeAlways(uri, localName, rawName, type, value, false);
    }
  }

  /**
   * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
   */
  public void notationDecl(String arg0, String arg1, String arg2)
      throws SAXException {
    // This method just provides a definition to satisfy the interface
    // A particular sub-class of SerializerBase provides the implementation (if desired)
  }

  /**
   * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String,
   * java.lang.String, java.lang.String)
   */
  public void unparsedEntityDecl(
      String arg0,
      String arg1,
      String arg2,
      String arg3)
      throws SAXException {
    // This method just provides a definition to satisfy the interface
    // A particular sub-class of SerializerBase provides the implementation (if desired)
  }

  /**
   * If set to false the serializer does not expand DTD entities,
   * but leaves them as is, the default value is true.
   */
  public void setDTDEntityExpansion(boolean expand) {
    // This method just provides a definition to satisfy the interface
    // A particular sub-class of SerializerBase provides the implementation (if desired)
  }

}
